﻿using System;
using System.Collections.Generic;
using System.Linq;
using Zeta.Bot.Navigation;
using Zeta.Common;
using Zeta.Game;
using Zeta.Game.Internals.Actors;
using Zeta.TreeSharp;

namespace KadalaSpree.XCore
{
    public static partial class X
    {
        public static class Movement
        {

            #region Delegates

            public delegate void OnDestination(MoveArgs args);
            public delegate void OnFailed(MoveArgs args);
            public delegate void OnMoved(MoveArgs args);

            #endregion

            /// <summary>
            /// Move to an actor by name
            /// </summary>
            public static RunStatus Move(ActorName name, OnDestination arrived = null, OnMoved moved = null, OnFailed failed = null)
            {
                var actor = Actors.GetActorByName(name);
                return Move(actor, arrived, moved, failed);
            }

            /// <summary>
            /// Move to a position
            /// </summary>
            public static RunStatus Move(Vector3 position, string name = "", OnDestination arrived = null, OnMoved moved = null, OnFailed failed = null)
            {
                arrived = arrived ?? DefaultArrived;
                moved = moved ?? DefaultMoved;
                failed = failed ?? DefaultFailed;

                var args = new MoveArgs
                {
                    Name = ActorName.Point,
                    Position = position,
                    Actor = null
                };

                if (ZetaDia.Me.Position.Distance2D(position) > Settings.MaxNavigateDistance)
                {
                    failed(args);
                    return RunStatus.Failure;
                }

                var moveResult = SafeNavigatorMove(args.Position, args.Name.ToString());

                return HandleMoveResult(moveResult, args, arrived, moved, failed);
            }

            /// <summary>
            /// Move to an actor
            /// </summary>
            public static RunStatus Move(XActor actor, OnDestination arrived = null, OnMoved moved = null, OnFailed failed = null)
            {                
                arrived = arrived ?? DefaultArrived;
                moved = moved ?? DefaultMoved;
                failed = failed ?? DefaultFailed;

                var args = new MoveArgs
                {
                    Name = actor.ActorName,
                    Position = actor.Position,
                    Actor = actor
                };

                if (!actor.IsValid)
                {
                    failed(args);
                    return RunStatus.Failure;
                }

                var moveResult = SafeNavigatorMove(args.Position, args.Name.ToString());

                return HandleMoveResult(moveResult, args, arrived, moved, failed);
            }


            private static RunStatus HandleMoveResult (MoveResult moveResult, MoveArgs args, OnDestination arrived, OnMoved moved, OnFailed failed)
            {
                if ( moveResult == MoveResult.ReachedDestination || ZetaDia.Me.Position.Distance(args.Position) < Settings.InteractRange)
                {
                    Navigator.PlayerMover.MoveStop();
                    arrived(args);
                    //_visited.Add(args.Name);
                    return RunStatus.Failure;
                }

                switch (moveResult)
                {
                    case MoveResult.PathGenerating:
                    case MoveResult.PathGenerated:
                    case MoveResult.UnstuckAttempt:
                        return RunStatus.Running;

                    case MoveResult.Moved:
                        moved(args);
                        return RunStatus.Running;          

                    default:
                        failed(args);
                        return RunStatus.Failure;
                }

            }

            /// <summary>
            /// Moves to a given position
            /// </summary>
            private static MoveResult SafeNavigatorMove (Vector3 destination, string destinationName = null, bool useRaycast = true)
            {
                if (!ZetaDia.IsInGame || !ZetaDia.Me.IsValid || ZetaDia.Me.IsDead || ZetaDia.IsLoadingWorld || !ZetaDia.Service.IsValid || !ZetaDia.WorldInfo.IsValid)
                {
                    return MoveResult.Failed;
                }
                try
                {
                    return Navigator.MoveTo(destination, destinationName, useRaycast);
                }
                catch (Exception ex)
                {
                    Logger.Instance.Log("{0}", ex);
                    return MoveResult.Failed;
                }
            }

        }

        #region Default Event Handlers

        private static void DefaultMoved(MoveArgs args)
        {
            Logger.Instance.Log("Moving to {0}: x={1} y={2} z={3} distance={4}",
                args.Name,
                args.Position.X,
                args.Position.Y,
                args.Position.Z,
                args.Position.Distance2D(ZetaDia.Me.Position));
        }

        private static void DefaultArrived(MoveArgs args)
        {
            Logger.Instance.Log("Arrived at {0}", args.Name);
        }

        private static void FinalDestinationReached(MoveArgs args)
        {
            Logger.Instance.Log("All destinations have been visisted");
        }

        private static void DefaultFailed(MoveArgs args)
        {
            Logger.Instance.Log("Failed moving to {0}", args.Name);
        }

        #endregion

    }

    public class MoveArgs
    {
        public ActorName Name { get; set; }
        public Vector3 Position { get; set; }        
        public XActor Actor { get; set; }
    }
}


///// <summary>
///// Move to a specific destination
///// </summary>
//public static RunStatus Move (ActorName name, OnDestination arrived = null, OnMoved moved = null, OnFailed failed = null)
//{
//    arrived = arrived ?? DefaultDestinationReached;
//    moved = moved ?? DefaultMoved;
//    failed = failed ?? DefaultFailed;

//    var args = new MoveArgs
//    {
//        Name = name
//    };

//    XActor actor = Actors.GetActorByName(args.Name);

//    if (!actor.IsValid)
//    {
//        failed(args);
//        return RunStatus.Failure;
//    }

//    args.Position = actor.Position;

//    MoveResult moveResult = MoveTo(args.Position, args.Name.ToString());
//    var result = RunStatus.Success;

//    if (ZetaDia.Me.Position.Distance(args.Position) < Settings.InteractRange || moveResult == MoveResult.ReachedDestination)
//    {
//        Navigator.PlayerMover.MoveStop();
//        arrived(args);
//        _visited.Add(name);
//        return RunStatus.Success;
//    }

//    switch (moveResult)
//    {
//        case MoveResult.PathGenerating:
//        case MoveResult.PathGenerated:
//        case MoveResult.UnstuckAttempt:
//            result = RunStatus.Running;
//            break;

//        case MoveResult.Moved:
//            moved(args);
//            result = RunStatus.Running;
//            break;

//        case MoveResult.Failed:
//        case MoveResult.PathGenerationFailed:
//            failed(args);
//            result = RunStatus.Failure;
//            break;
//    }

//    return result;
//}

//public static List<ActorName> _destinations = new List<ActorName>();
//private static ActorName _currentDestination = ActorName.None;
//private static bool _moving;
//internal static List<ActorName> _visited = new List<ActorName>();

///// <summary>
///// Move to a specific destination
///// </summary>
//public static RunStatus MoveSequence (
//    List<ActorName> destinations,
//    OnDestination finalDestinationDelegate = null,
//    OnDestination destinationDelegate = null,
//    OnMoved movedDelegate = null,
//    OnFailed failedDelegate = null)
//{
//    finalDestinationDelegate = finalDestinationDelegate ?? FinalDestinationReached;
//    destinationDelegate = destinationDelegate ?? DefaultDestinationReached;
//    movedDelegate = movedDelegate ?? DefaultMoved;
//    failedDelegate = failedDelegate ?? DefaultFailed;

//    //// if not currently moving and there are no destinations
//    //if (!_shouldMove && _destinations.Any())
//    //{
//    //    // started move set
//    //    X.Logger.Log("{0} New Destinations", _destinations.Count);
//    //    _moveSetStarted = true;
//    //    _shouldMove = true;
//    //}

//    // If we're not moving and have no destinations available
//    if (!_moving && !destinations.Any())
//        _moving = false;

//    // if not currently moving and there are destinations available
//    if (!_moving && destinations.Any())
//    {
//        // grab a new destination
//        _currentDestination = destinations.First();
//        destinations.RemoveAt(0);
//        Logger.Log("Setting destination {0}", _currentDestination);
//        _moving = true;
//    }

//    // if we have a valid destination
//    if (_moving && _currentDestination != ActorName.None)
//    {
//        RunStatus result = Move(_currentDestination, destinationDelegate, movedDelegate, failedDelegate);
//        _moving = (result == ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Running);

//        if (result == ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Failure && destinations.Any())
//        {
//            // failed but we have more places to go - skip.                    
//            return ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Success;
//        }

//        return result;
//    }

//    // finished
//    finalDestinationDelegate(new MoveArgs());

//    // We have no destinations left, move sequence has failed
//    return ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Failure;
//}

///// <summary>
///// Move to a specific destination, by way of other destinations if required
///// </summary>
//public static RunStatus MovePriority (
//    ActorName destination,
//    List<ActorName> backupDestinations,
//    OnDestination finalDestinationDelegate = null,
//    OnDestination destinationDelegate = null,
//    OnMoved movedDelegate = null,
//    OnFailed failedDelegate = null)
//{
//    finalDestinationDelegate = finalDestinationDelegate ?? DefaultArrived;
//    destinationDelegate = destinationDelegate ?? DefaultArrived;
//    movedDelegate = movedDelegate ?? DefaultMoved;
//    failedDelegate = failedDelegate ?? DefaultFailed;

//    XActor actor = Actors.GetActorByName(destination);

//    if (actor.IsValid)
//        return Move(destination, finalDestinationDelegate, movedDelegate, failedDelegate);

//    Logger.Log("MovePriority Actor {0} not found", destination);

//    if (backupDestinations.Any())
//    {
//        RunStatus result = MoveSequence(backupDestinations, finalDestinationDelegate, destinationDelegate, movedDelegate, failedDelegate);

//        if (result == ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Running)
//            _moving = true;
//        if (result == ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Failure)
//        {
//            // failed or failed to generate path to destination - skip.
//        }

//        return result;
//    }

//    Logger.Log("Failed to reach {0}", destination);

//    return ((arrived, moved, failed, args, moveResult) => HandleMoveResult(moveResult, args, arrived, moved, failed)).Failure;
//}
